// Copyright (c) 2009-2012 Sandvine Incorporated.  All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.

#ifndef PROFILER_PRINTER_H
#define PROFILER_PRINTER_H

#include "Profiler.h"
#include "Image.h"
#include "Process.h"
#include <cstdio>
#include <cassert>

class ProfilePrinter
{
protected:
    FILE * m_outfile;
    uint32_t m_maxDepth;

    void printLineNumbers(const Profiler & profiler, const LineLocationList & functionLocation);

public:
    ProfilePrinter(FILE * file, uint32_t maxDepth)
      : m_outfile(file),
	m_maxDepth(maxDepth)
    {
    }

    uint32_t maxDepth() const
    {
	return m_maxDepth;
    }

    virtual void printProfile(const Profiler & profiler,
                              const Process::ActiveProcessList & activeProcessList) = 0;

    virtual ~ProfilePrinter()
    {
	if(m_outfile != stdout)
            fclose(m_outfile);
    }
};

class FlatProfilePrinter : public ProfilePrinter
{
public:
    FlatProfilePrinter(FILE * file)
	: ProfilePrinter(file, 1)
    {
    }

    virtual void printProfile(const Profiler & profiler,
                              const Process::ActiveProcessList & activeProcessList);
};

template <class ProcessStrategy>
class CallchainProfilePrinter : public ProfilePrinter
{
    int m_threshold;
    bool m_printBoring;

    void printCallChain(const Profiler & profiler, Process& process, Callchain & chain, int depth);
    bool isCallChainBoring(Process& process, Callchain & chain);

public:
    CallchainProfilePrinter(FILE * file, uint32_t maxDepth, int threshold, bool printBoring)
      : ProfilePrinter(file, maxDepth),
	m_threshold(threshold),
	m_printBoring(printBoring)
    {
    }

    virtual void printProfile(const Profiler & profiler,
                              const Process::ActiveProcessList & activeProcessList);
};

struct LeafProcessStrategy
{
    typedef std::vector<Location>::iterator iterator;

    iterator begin(std::vector<Location> & vec)
    {
	return vec.begin();
    }

    iterator end(std::vector<Location> & vec)
    {
	return vec.end();
    }

    void processEnd(std::vector<Location> & vec, CallchainMap & callchainMap, const Callchain & callchain)
    {
    }
};

struct RootProcessStrategy
{
    typedef std::vector<Location>::reverse_iterator iterator;

    iterator begin(std::vector<Location> & vec)
    {
	iterator it = vec.rbegin();

	while(!it->isMapped() && it != end(vec)) {
            ++it;
	}

	return it;
    }

    iterator end(std::vector<Location> & vec)
    {
	return vec.rend();
    }

    void processEnd(std::vector<Location> & vec, CallchainMap & callchainMap, const Callchain & callchain)
    {
	FunctionLocationMap & selfCount = callchainMap[callchain];
	std::pair<FunctionLocationMap::iterator, bool> inserted =
            selfCount.insert(FunctionLocationMap::value_type("[self]", FunctionLocation(vec.front())));
	if(!inserted.second)
            inserted.first->second += vec.front();
	else
            inserted.first->second.setFunctionName("[self]");
    }
};

template <class ProcessStrategy>
bool
CallchainProfilePrinter<ProcessStrategy>::isCallChainBoring(Process& process, Callchain & chain)
{
    std::vector<FunctionLocation> functions;
    process.getCallers(chain, functions);

    if(functions.size() > 1)
	return false;
    else if(functions.size() == 0)
	return true;
    else
    {
	chain.push_back(functions.front().getFunctionName());
	bool boring = isCallChainBoring(process, chain);
	assert(chain.back() == functions.front().getFunctionName());
	chain.pop_back();
	return boring;
    }
}

template <class ProcessStrategy>
void
CallchainProfilePrinter<ProcessStrategy>::printCallChain(const Profiler & profiler, Process& process, Callchain & chain, int depth)
{
    std::vector<FunctionLocation> functions;
    unsigned total_samples = process.getCallers(chain, functions);

    bool isBoring = false;
    if(!m_printBoring && functions.size() == 1) {
	isBoring = isCallChainBoring(process, chain);
    }

    std::vector<FunctionLocation>::iterator it = functions.begin();
    for(; it != functions.end(); ++it)
    {
	double parent_percent = (it->getCount() * 100.0) / total_samples;
	double total_percent = (it->getCount() * 100.0) / process.getSampleCount();

	if(total_percent < m_threshold)
            continue;

	char * functionName = Image::demangle(it->getFunctionName());

	for(int i = 0; i < depth; i++) {
            fprintf(m_outfile, "  ");
	}
	fprintf(m_outfile, "[%d] %.2f%% %.2f%%(%d/%d) %s", depth,
            parent_percent, total_percent, it->getCount(),
            total_samples, functionName);
	printLineNumbers(profiler, it->getLineLocationList());
	fprintf(m_outfile, "\n");
	free(functionName);

	if(!isBoring)
	{
            chain.push_back(it->getFunctionName());
            printCallChain(profiler, process, chain, depth+1);
            assert(chain.back() == it->getFunctionName());
            chain.pop_back();
	}
    }
}

template <class ProcessStrategy>
void
CallchainProfilePrinter<ProcessStrategy>::printProfile(const Profiler & profiler,
    const Process::ActiveProcessList & activeProcessList)
{
    fprintf(m_outfile, "Events processed: %u\n", profiler.getSampleCount());

    for ( Process::ActiveProcessList::const_iterator it = activeProcessList.begin();
	it != activeProcessList.end(); ++it )
    {
	Process& process( **it );
	LocationList locationList;
	process.collectLocations(locationList);
	std::sort(locationList.begin(), locationList.end());
	Image::mapAllLocations(locationList);
	Process::mapAllFunctions<ProcessStrategy>(locationList, ProcessStrategy());

	fprintf(m_outfile, "\nProcess: %6u, %s, total: %u (%6.2f%%)\n", process.getPid(), process.getName().c_str(),
            process.getSampleCount(), (process.getSampleCount() * 100.0) / profiler.getSampleCount());
	FunctionList functionList;
	process.getFunctionList(functionList);
	for ( FunctionList::iterator it = functionList.begin(); it != functionList.end(); ++it )
	{
            FunctionLocation& functionLocation( *it );

            double percent = (functionLocation.getCount() * 100.0) / process.getSampleCount();
            if(percent >= m_threshold)
            {
		char * functionName = Image::demangle(functionLocation.getFunctionName());
		fprintf(m_outfile, "[0] %.2f%% %.2f%%(%d/%d) %s", percent, percent,
                    functionLocation.getCount(), process.getSampleCount(), functionName);
		printLineNumbers(profiler, functionLocation.getLineLocationList());
		fprintf(m_outfile, "\n");

		Callchain chain;
		chain.push_back(functionLocation.getFunctionName());
		printCallChain(profiler, process, chain, 1);
		free(functionName);
            }
	}
    }
}

typedef CallchainProfilePrinter<LeafProcessStrategy> LeafProfilePrinter;
typedef CallchainProfilePrinter<RootProcessStrategy> RootProfilePrinter;

#endif
